package com.aplink.generic.google.maps;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import android.view.View;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

/**
 * Keeps track of collections of markers on the map. Delegates all
 * Marker-related events to each collection's individually managed listeners.
 * <p/>
 * All marker operations (adds and removes) should occur via its collection
 * class. That is, don't add a marker via a collection, then remove it via
 * Marker.remove()
 */
public class MarkerManager implements GoogleMap.OnInfoWindowClickListener,
        GoogleMap.OnMarkerClickListener, GoogleMap.OnMarkerDragListener,
        GoogleMap.InfoWindowAdapter {
    public class Collection {
        private GoogleMap.InfoWindowAdapter mInfoWindowAdapter;
        private GoogleMap.OnInfoWindowClickListener mInfoWindowClickListener;
        private GoogleMap.OnMarkerClickListener mMarkerClickListener;
        private GoogleMap.OnMarkerDragListener mMarkerDragListener;
        private final Set<Marker> mMarkers = new HashSet<Marker>();

        public Collection() {
        }

        public Marker addMarker(final MarkerOptions opts) {
            final Marker marker = mMap.addMarker(opts);
            mMarkers.add(marker);
            mAllMarkers.put(marker, Collection.this);
            return marker;
        }

        public void clear() {
            for (final Marker marker : mMarkers) {
                marker.remove();
                mAllMarkers.remove(marker);
            }
            mMarkers.clear();
        }

        public java.util.Collection<Marker> getMarkers() {
            return Collections.unmodifiableCollection(mMarkers);
        }

        public boolean remove(final Marker marker) {
            if (mMarkers.remove(marker)) {
                mAllMarkers.remove(marker);
                marker.remove();
                return true;
            }
            return false;
        }

        public void setOnInfoWindowAdapter(
                final GoogleMap.InfoWindowAdapter infoWindowAdapter) {
            mInfoWindowAdapter = infoWindowAdapter;
        }

        public void setOnInfoWindowClickListener(
                final GoogleMap.OnInfoWindowClickListener infoWindowClickListener) {
            mInfoWindowClickListener = infoWindowClickListener;
        }

        public void setOnMarkerClickListener(
                final GoogleMap.OnMarkerClickListener markerClickListener) {
            mMarkerClickListener = markerClickListener;
        }

        public void setOnMarkerDragListener(
                final GoogleMap.OnMarkerDragListener markerDragListener) {
            mMarkerDragListener = markerDragListener;
        }
    }

    private final Map<Marker, Collection> mAllMarkers = new HashMap<Marker, Collection>();
    private final GoogleMap mMap;

    private final Map<String, Collection> mNamedCollections = new HashMap<String, Collection>();

    public MarkerManager(final GoogleMap map) {
        this.mMap = map;
    }

    /**
     * Gets a named collection that was created by
     * {@link #newCollection(String)}
     * 
     * @param id
     *            the unique id for this collection.
     */
    public Collection getCollection(final String id) {
        return mNamedCollections.get(id);
    }

    @Override
    public View getInfoContents(final Marker marker) {
        final Collection collection = mAllMarkers.get(marker);
        if ((collection != null) && (collection.mInfoWindowAdapter != null)) {
            return collection.mInfoWindowAdapter.getInfoContents(marker);
        }
        return null;
    }

    @Override
    public View getInfoWindow(final Marker marker) {
        final Collection collection = mAllMarkers.get(marker);
        if ((collection != null) && (collection.mInfoWindowAdapter != null)) {
            return collection.mInfoWindowAdapter.getInfoWindow(marker);
        }
        return null;
    }

    public Collection newCollection() {
        return new Collection();
    }

    /**
     * Create a new named collection, which can later be looked up by
     * {@link #getCollection(String)}
     * 
     * @param id
     *            a unique id for this collection.
     */
    public Collection newCollection(final String id) {
        if (mNamedCollections.get(id) != null) {
            throw new IllegalArgumentException("collection id is not unique: "
                    + id);
        }
        final Collection collection = new Collection();
        mNamedCollections.put(id, collection);
        return collection;
    }

    @Override
    public void onInfoWindowClick(final Marker marker) {
        final Collection collection = mAllMarkers.get(marker);
        if ((collection != null)
                && (collection.mInfoWindowClickListener != null)) {
            collection.mInfoWindowClickListener.onInfoWindowClick(marker);
        }
    }

    @Override
    public boolean onMarkerClick(final Marker marker) {
        final Collection collection = mAllMarkers.get(marker);
        if ((collection != null) && (collection.mMarkerClickListener != null)) {
            return collection.mMarkerClickListener.onMarkerClick(marker);
        }
        return false;
    }

    @Override
    public void onMarkerDrag(final Marker marker) {
        final Collection collection = mAllMarkers.get(marker);
        if ((collection != null) && (collection.mMarkerDragListener != null)) {
            collection.mMarkerDragListener.onMarkerDrag(marker);
        }
    }

    @Override
    public void onMarkerDragEnd(final Marker marker) {
        final Collection collection = mAllMarkers.get(marker);
        if ((collection != null) && (collection.mMarkerDragListener != null)) {
            collection.mMarkerDragListener.onMarkerDragEnd(marker);
        }
    }

    @Override
    public void onMarkerDragStart(final Marker marker) {
        final Collection collection = mAllMarkers.get(marker);
        if ((collection != null) && (collection.mMarkerDragListener != null)) {
            collection.mMarkerDragListener.onMarkerDragStart(marker);
        }
    }

    /**
     * Removes a marker from its collection.
     * 
     * @param marker
     *            the marker to remove.
     * @return true if the marker was removed.
     */
    public boolean remove(final Marker marker) {
        final Collection collection = mAllMarkers.get(marker);
        return (collection != null) && collection.remove(marker);
    }
}
